package com.hailong.hadoop.doublebuffer;

import java.util.LinkedList;

/**
 * 双缓存方案，因思考一下三个问题
 * 1） 本来是直接写磁盘的，现在写内存是否会导致数据丢失风险
 * 2）
 */
public class FSEditLog {
    private long txid=0L;
    private DoubleBuffer editLogBuffer=new DoubleBuffer();
    //当前是否正在往磁盘里面刷写数据
    private volatile Boolean isSyncRunning = false;
    private volatile Boolean isWaitSync = false;

    private volatile Long syncMaxTxid = 0L;
    /**
     * 一个线程 就会有自己一个ThreadLocal的副本
     */
    private ThreadLocal<Long> localTxid=new ThreadLocal<Long>();
    /**
     * 90%是与HDFS的代码是接近的
     * 5%
     * 5% 我把源码给优化了一下。
     * 记录元数据日志方法
     * @param content
     */
    public void logEdit(String content){//mkdir /data
        //保证ID，递增，独一无二
        //线程一
        //线程二
        //线程三
        //线程四
        //线程五
        synchronized (this){ //里面的4个操作都是纯内存的操作，所以这个虽然加了锁，但是性能很好。
            //线程一 1
            //线程二 2
            //线程二 3
            //线程四 4
            //线程五 5
            txid++;
            //线程一 1
            //线程二 2
            //线程三 3
            //线程四 4
            //线程五 5
            localTxid.set(txid);
            //
            EditLog log = new EditLog(txid, content);
            //把日志对象写到内存1
            editLogBuffer.write(log);
        } //释放锁

        logSync();
    }

    private  void logSync(){
        //线程一 进来
        //线程二 进来
        //线程三 进来
        //线程四 进来
        //线程五 进来
        synchronized (this){
            //是否正在同步元数据
            //线程二：确实有人在同步数据
            //线程三：确实有人在同步数据
            //线程四：确实有人在同步数据
            //线程五：确实有人在同步数据
            if(isSyncRunning){
                //线程二：获取副本 2
                //线程三：获取副本 3
                //线程四：获取副本 4
                //线程五：获取副本 5
                long txid = localTxid.get();
                //线程二：2 <= 3
                //线程三：3 <= 3
                //线程四：4 <= 3
                //线程四：5 <= 3
                if(txid <= syncMaxTxid){
                    //线程二：直接return
                    //线程三：直接return
                    return;
                }
                //线程四：
                //线程五：
                if(isWaitSync){
                    return;
                }
                //线程四：赋值
                isWaitSync = true;

                while(isSyncRunning){
                    try {
                        //线程4 wait  释放锁
                        //1)时间到了 2）唤醒了
                        wait(2000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                isWaitSync = false;
            }

            /**
             * 线程一：交换内存
             */
            editLogBuffer.setReadyToSync();



            if(editLogBuffer.syncBuffer.size() > 0) {
                //线程一
                //获取内存2里面的最大的一个日志编号  3
                syncMaxTxid = editLogBuffer.getSyncMaxTxid();
            }
            //修改状态值
            isSyncRunning = true;

        } //线程一 释放锁


        //线程一 在这儿写磁盘
        editLogBuffer.flush();//这个操作比较耗时，几十ms

        //线程一
        synchronized (this) {
            //重新赋值
            isSyncRunning = false;
            //唤醒等待的线程
            notify();
        }

    }


    /**
     * 用面向对象的思想，把每一条日志，都看作一个对象。
     */
    class EditLog{
        //事务ID号
        long txid;
        //元数据日志的内容
        // mkdir /root/data/table
        String content;

        //构造函数
        public EditLog(long txid,String content){
            this.txid = txid;
            this.content = content;
        }


        @Override
        public String toString() {
            return "EditLog{" +
                    "txid=" + txid +
                    ", content='" + content + '\'' +
                    '}';
        }
    }

    /**
     * 双缓冲的方案
     */
    class DoubleBuffer{
        //内存1
        LinkedList<EditLog> currentBuffer = new LinkedList<EditLog>();
        //内存2
        LinkedList<EditLog> syncBuffer= new LinkedList<EditLog>();

        /**
         * 把数据写入到当前内存
         * @param log
         */
        public void write(EditLog log){
            currentBuffer.add(log);
        }


        /**
         * 交换内存
         */
        public void setReadyToSync(){
            LinkedList<EditLog> tmp= currentBuffer;
            currentBuffer = syncBuffer;
            syncBuffer = tmp;
        }

        /**
         * 获取内存2里面最大的日志的编号（ID）
         * @return
         */
        public Long getSyncMaxTxid(){
            return syncBuffer.getLast().txid;
        }

        /**
         * 把数据写道磁盘上面
         */
        public void flush(){
            for(EditLog log:syncBuffer){
                System.out.println("存入磁盘日志信息："+log);
            }
            //清空内存里面的数据
            syncBuffer.clear();
        }
    }
}
